Example - Meal planning

This notebook shows features of streprogen, the Python strength program generator.

Contributions to the code are welcome. :)

[1]:
!pip install streprogen matplotlib --quiet

Imports

[2]:
from streprogen import Meal, Food, Mealplan

Set up foods

  • The attributes protein, fat, carbs and kcal below are per 100 grams.
  • I recommend ignoring foods that you only eat small quantities of, e.g. ketchup.
[3]:
all_foods = [
    Food(name="burger", protein=15.0, fat=18.0, carbs=2.0, kcal=230,
         price_per_product=87.3, grams_per_product=800),

    Food(name="cottage cheese", protein=13.0, fat=2.0, carbs=2.1, kcal=79,
         price_per_product=24.4, grams_per_product=400),

    Food(name="egg", protein=13.0, fat=10.6, carbs=0.3, kcal=149,
         price_per_product=32.9, grams_per_product=690),

    Food(name="chicken filet", protein=19.0, fat=1.8, carbs=0.3, kcal=94,
         price_per_product=260.0, grams_per_product=2500),

    Food(name="bread", protein=11.0, fat=4.8, carbs=36.0, kcal=245,
         price_per_product=39.5, grams_per_product=750),

    Food(name="jasmin rice", protein=2.7, fat=0.1, carbs=31.1, kcal=136,
         price_per_product=45.8, grams_per_product=1000),

    Food(name="milk", protein=3.5, fat=0.5, carbs=4.5, kcal=37,
         price_per_product=16.4, grams_per_product=1000),

    Food(name="chocolate", protein=8.1, fat=33.0, carbs=55.0, kcal=550,
         price_per_product=38.6, grams_per_product=200),

    Food(name="muesli", protein=9.0, fat=4.8, carbs=63.0, kcal=351,
         price_per_product=23.1, grams_per_product=750),

    Food(name="PF whey", protein=71.8, fat=8.1, carbs=7.9, kcal=377,
         price_per_product=599.0, grams_per_product=3000),

    Food(name="sweet and sour sauce", protein=0.6, fat=0.1, carbs=20, kcal=85,
         price_per_product=14.9, grams_per_product=500),

    Food(name="mixed nuts", protein=13, fat=26, carbs=47, kcal=464,
         price_per_product=39.7, grams_per_product=350),

    Food(name="yogurt", protein=3.7, fat=3.1, carbs=10.5, kcal=84,
         price_per_product=17.0, grams_per_product=600),
]

Aggregate foods to meals

  • Once the foods are created, they are aggregated to meals.
  • A meal consists of many foods.
[4]:
# A trick to look up food by name
foods = {food.name: food for food in all_foods}
[5]:
meals = [

    # Mixed nuts from the store. It's not a discrete meal, since it's easy to weight up
    # arbitrary weights of mixed nuts. Setting the base quantity to 10 grams makes it easier to
    # work with. If the result is 7.3 x 'base meals' for this food, it means 73grams of nuts
    Meal(name="mixed nuts", foods={foods["mixed nuts"]: 10}, discrete=False),

    # This meal is created using two foods: 150g yogurt (one small container) and 40g muesli.
    # The meal is discrete, since if you open a little yogurt you want to eat the entire thing.
    Meal(name="yogurt w/ muesli",foods={foods["yogurt"]: 150, foods["muesli"]: 40}, discrete=True),

    # A dinner-like meal. Here the proportions of the ingredients are normalized.
    # Since 40.7 + 11.5 + 47.8 = 100, weighing up arbitrary meal sizes is easy.
    # If the result is 3.1 x 'base meals', it means 310 grams
    Meal(name="chicken w/ sweet&sour",
         foods={
            foods["chicken filet"]: 40.7,
            foods["sweet and sour sauce"]: 11.5,
            foods["jasmin rice"]: 47.8,
        }, discrete=False,
    ),

    # Hamburger with bread. Discrete since you either eat 0, 1, 2, 3, .....
    Meal(name="hamburger", foods={foods["bread"]: 40, foods["burger"]: 80},discrete=True,),

    # A really boring meal - but it's a demonstration after all
    Meal(name="egg", foods={foods["egg"]: 70}, discrete=True),

    # Protein shake. Easy to weigh up arbitrary portions, so not discrete
    Meal(name="scoop of whey with milk",
         foods={foods["PF whey"]: 25, foods["milk"]: 150},
        discrete=False,
    ),

    # One small container with yogurt and cottage cheese
    Meal(name="yogurt w/ ct.cheese",
        foods={foods["yogurt"]: 150, foods["cottage cheese"]: 100},
        discrete=True,
    ),

    # Let's add chocolate, just for fun
    Meal(name="chocolate", foods={foods["chocolate"]: 100},discrete=False,)
]

Create a single-day meal plan

The meal plan will balance three considerations:

  • The overall price of the meal plan (the importance of optimizing this is weighted by weight_price).
  • How well the dietary constraints are satisfied (weighted by weight_nutrients).
  • Equal meal sizes as measured in calories (weighted by weight_range).
[6]:
# Set dietary contraints.
# Valid keys are: 'kcal', 'protein', 'carbs', 'fat'
dietary_constraints = {"kcal": (1800, 1800),   # Lower limit and upper limit at 1800 kcal per day
                       "protein": (100, None)} # At least 100 grams of protein per day

# Create a meal plan
meal_plan = Mealplan(meals,
                     dietary_constraints,

                     # The number of meals every day
                     num_meals=4,

                     # The number of days
                     num_days=1,

                     # Relative weighting for the optimization
                     # Higher weights mean higher priority
                     weight_price=0.1,
                     weight_nutrients=2.0,
                     weight_meal_sizes=0.75,
                    )

Run optimization and print results

  • If you’re unhappy with these results, try changing the weights above and re-run.
[7]:
meal_plan.render()

print(meal_plan.to_txt(verbose=True))
----------------------------------------------------------------
Meal plan

Program parameters

  dietary_constraints: {'kcal': (1800, 1800), 'protein': (100, None)}
  num_meals: 4
  num_days: 1

  weight_price: 0.1
  weight_nutrients: 2.0
  weight_meal_sizes: 0.75

  optimization_time: 0.291 s
  optimization_iterations: 2551

----------------------------------------------------------------
Meal information (used meals only)

  'chicken w/ sweet&sour' (100 grams)
    - 40.7 grams of 'chicken filet'
    - 11.5 grams of 'sweet and sour sauce'
    - 47.8 grams of 'jasmin rice'
  'chocolate' (100 grams)
    - 100 grams of 'chocolate'
  'mixed nuts' (10 grams)
    - 10 grams of 'mixed nuts'
  'scoop of whey with milk' (175 grams)
    - 25 grams of 'PF whey'
    - 150 grams of 'milk'

----------------------------------------------------------------
Daily meal plan statistics

 - price: 76 [76]
 - protein: 125 [125]
 - fat: 64 [64]
 - carbs: 186 [186]
 - kcal: 1800 [1800]

----------------------------------------------------------------
Program

 Day 1

   - 4   x 'chicken w/ sweet&sour' (base meal of 100 grams)
   - 9.7 x 'mixed nuts' (base meal of 10 grams)
   - 0.8 x 'chocolate' (base meal of 100 grams)
   - 3   x 'scoop of whey with milk' (base meal of 175 grams)

   Statistics

     - price: 76 [27, 11, 16, 22]
     - protein: 125 [36, 13, 7, 70]
     - fat: 64 [3, 25, 27, 8]
     - carbs: 186 [69, 46, 45, 26]
     - kcal: 1800 [450, 450, 450, 450]

----------------------------------------------------------------

Create a multi-day meal plan

The meal plan will balance three considerations:

  • The overall price of the meal plan (weighted by weight_price)
  • How well the dietary constraints are satisfied (weighted by weight_nutrients)
  • Equal meal sizes as measured in calories (weighted by weight_range)

Notice the introduction of meal_limits below. Without it, it makes no sense to create a multi-day meal program, since the optimal meal plan will be a single optimal day copied over several days.

Notes on computation

  • The optimization routine solves an extremely difficult problem.
  • The solution time depends crucially on the number of discrete meals. Non-discrete meals are easier computationally.
  • The solver will return the best solution found within the time limit. Set the time_limit_secs parameter higher to increase solve time.
  • I recommend optimizing over 3-4 days and cycling those days.
[8]:
# Set dietary contraints.
# Valid keys are: 'kcal', 'protein', 'carbs', 'fat'
dietary_constraints = {"kcal": (1800, 1800),   # Lower limit and upper limit at 1800 kcal per day
                       "protein": (100, None), # At least 100 grams of protein per day
                       "carbs": (None, 150)}   # At most 150 grams of carbohydrates per day


meal_limits = {"chocolate": (None, 1), # Chocolate at most once
              "hamburger": (1, None),  # Hamburger at least once
              "mixed nuts": (2, 2),}   # Mixed nuts exactly twice

# Create a meal plan
multi_day_meal_plan = Mealplan(meals,
                     dietary_constraints,

                     # Limits on the meals
                     meal_limits=meal_limits,

                     # The number of meals every day
                     num_meals=4,

                     # The number of days
                     num_days=4,

                     # Relative weighting for the optimization
                     # Higher weights mean higher priority
                     weight_price=0.1,
                     weight_nutrients=2.0,
                     weight_meal_sizes=0.75,
                    )

Run optimization and print results

If you’re unhappy with these results:

  • Try changing the weights above and re-run.
  • Try changing the time limit and re-run. The solver will typically find great solutions in 10 seconds.
[9]:
multi_day_meal_plan.render(time_limit_secs=10)

print(multi_day_meal_plan.to_txt(verbose=True))
----------------------------------------------------------------
Meal plan

Program parameters

  dietary_constraints: {'kcal': (1800, 1800), 'protein': (100, None), 'carbs': (None, 150)}
  num_meals: 4
  num_days: 4

  weight_price: 0.1
  weight_nutrients: 2.0
  weight_meal_sizes: 0.75

  optimization_time: 10.227 s
  optimization_iterations: 111592

----------------------------------------------------------------
Meal information (used meals only)

  'chicken w/ sweet&sour' (100 grams)
    - 40.7 grams of 'chicken filet'
    - 11.5 grams of 'sweet and sour sauce'
    - 47.8 grams of 'jasmin rice'
  'chocolate' (100 grams)
    - 100 grams of 'chocolate'
  'egg' (70 grams)
    - 70 grams of 'egg'
  'hamburger' (120 grams)
    - 40 grams of 'bread'
    - 80 grams of 'burger'
  'mixed nuts' (10 grams)
    - 10 grams of 'mixed nuts'
  'scoop of whey with milk' (175 grams)
    - 25 grams of 'PF whey'
    - 150 grams of 'milk'
  'yogurt w/ ct.cheese' (250 grams)
    - 150 grams of 'yogurt'
    - 100 grams of 'cottage cheese'

----------------------------------------------------------------
Daily meal plan statistics

 - price: 312 [74, 75, 87, 75]
 - protein: 611 [108, 158, 188, 158]
 - fat: 278 [88, 67, 55, 67]
 - carbs: 571 [142, 145, 139, 145]
 - kcal: 7198 [1800, 1800, 1799, 1800]

----------------------------------------------------------------
Program

 Day 1

   - 4.2 x 'chicken w/ sweet&sour' (base meal of 100 grams)
   - 0.9 x 'chocolate' (base meal of 100 grams)
   - 1   x 'hamburger' (base meal of 120 grams)
   - 5   x 'egg' (base meal of 70 grams)

   Statistics

     - price: 74 [28, 18, 11, 17]
     - protein: 108 [38, 8, 16, 46]
     - fat: 88 [3, 31, 16, 37]
     - carbs: 142 [73, 52, 16, 1]
     - kcal: 1800 [475, 522, 282, 522]

 Day 2

   - 4.1 x 'chicken w/ sweet&sour' (base meal of 100 grams)
   - 9.9 x 'mixed nuts' (base meal of 10 grams)
   - 3.1 x 'scoop of whey with milk' (base meal of 175 grams)
   - 4   x 'egg' (base meal of 70 grams)

   Statistics

     - price: 75 [28, 11, 23, 13]
     - protein: 158 [37, 13, 71, 36]
     - fat: 67 [3, 26, 9, 30]
     - carbs: 145 [70, 47, 27, 1]
     - kcal: 1800 [461, 461, 461, 417]

 Day 3

   - 4.3 x 'chicken w/ sweet&sour' (base meal of 100 grams)
   - 2   x 'yogurt w/ ct.cheese' (base meal of 250 grams)
   - 3.2 x 'scoop of whey with milk' (base meal of 175 grams)
   - 4   x 'egg' (base meal of 70 grams)

   Statistics

     - price: 87 [29, 21, 24, 13]
     - protein: 188 [39, 37, 75, 36]
     - fat: 55 [3, 13, 9, 30]
     - carbs: 139 [74, 36, 28, 1]
     - kcal: 1799 [486, 410, 486, 417]

 Day 4

   - 4.1 x 'chicken w/ sweet&sour' (base meal of 100 grams)
   - 9.9 x 'mixed nuts' (base meal of 10 grams)
   - 3.1 x 'scoop of whey with milk' (base meal of 175 grams)
   - 4   x 'egg' (base meal of 70 grams)

   Statistics

     - price: 75 [28, 11, 23, 13]
     - protein: 158 [37, 13, 71, 36]
     - fat: 67 [3, 26, 9, 30]
     - carbs: 145 [70, 47, 27, 1]
     - kcal: 1800 [461, 461, 461, 417]

----------------------------------------------------------------